Skip to content

feat(brand-protocol): verify_brand_claim — federated authoritative verification#4540

Open
bokelley wants to merge 11 commits into
mainfrom
bokelley/verification-endpoint-rfc
Open

feat(brand-protocol): verify_brand_claim — federated authoritative verification#4540
bokelley wants to merge 11 commits into
mainfrom
bokelley/verification-endpoint-rfc

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Draft RFC. Tracks #4521.

Summary

Replaces the originally-proposed typed-notification-endpoint design (push, event-based) with a federated trust surface (pull, interrogative). Three new brand-protocol tasks on the brand-agent let partners ask the brand authoritatively whether something belongs to it:

  • `verify_subsidiary_claim` — "Is this brand a subsidiary of yours?"
  • `verify_property` — "Is this site / app / property actually one of yours?"
  • `verify_trademark` — "Is this trademark one of yours?"

The mental model is DRM for brand identity: the brand owns the answers to "what's mine," and authorized partners can ask before they act. The mutual-assertion crawl model stays as decentralized backstop. The brand-agent verification path is the federated alternative for brands that opt in.

Why this shape

Three real gaps in the crawl-only model:

  1. Two-state visibility. Crawl answers "mutual or not." Can't surface pending review, disputed, licensed in.
  2. TTL-bound freshness. Consumer view is only as fresh as last crawl.
  3. One-sided pessimism. A leaf with TLS-verified identity gets downgraded to "unverified" until the parent edits its portfolio file.

The interrogative surface fixes all three. Authoritative answers, real-time, with the rich state surface partners actually need (especially `pending_review` and `disputed`).

What's in this PR

  • One proposal doc at `docs/brand-protocol/proposals/brand-verification-rfc.mdx` — motivation, design, trust model, open questions
  • Three task docs at `docs/brand-protocol/tasks/verify_*.mdx`
  • Seven schemas:
    • Shared status enum: `brand/verification-status.json`
    • Three request/response pairs
  • brand.json Conformance update: one SHOULD that agent path is preferred when advertised
  • docs.json nav updated under brand-protocol Tasks
  • Changeset noting draft RFC status

Validation

  • `npm run build:schemas` clean
  • `npm run test:schemas` 7/7
  • `npm run test:examples` 36/36
  • `npm run test:json-schema` 260/260
  • `npm run test:composed` 40/40
  • `npm run typecheck` clean

Open questions (in the RFC doc, repeated here for visibility)

  1. `verify_property` use_case scoping — keep?
  2. `pending_review` queue position / resolution window — public vs authorized-only?
  3. Agent-says-not_ours vs leaf-says-house — UI guidance for "disputed" rendering
  4. Rate-limiting policy — agent's call but spec sets expectations
  5. Bulk variants — defer or include in v1?

Test plan

  • Spec-owner review of the surface (interrogative vs notification framing)
  • Trust-model review (agent-wins, crawl-backstop)
  • Decision on the five open questions
  • Land only after the design review

Related

🤖 Generated with Claude Code

bokelley and others added 2 commits May 14, 2026 09:34
Draft RFC for a federated trust capability on the brand-agent — three
interrogative tools that let partners ask the brand authoritatively
whether something belongs to it. Reframes the email-based self-healing
SHOULD from PR #4505 as a richer pull-based DRM-for-brand-identity
surface.

New tasks (all brand-protocol, advertised in get_adcp_capabilities):
- verify_subsidiary_claim — replaces crawl inference for is-this-a-leaf
- verify_property — authoritative ownership of websites/apps/etc
- verify_trademark — licensing + jurisdiction + Nice class

Shared VerificationStatus enum captures rich state crawl cannot express:
owned / pending_review / disputed / not_ours / licensed_in / licensed_out
/ unknown. Public/authorized tier split mirrors get_brand_identity.

Cross-protocol: brand.json Conformance gains a SHOULD that when a house
publishes a brand-agent advertising these tasks, consumers prefer the
agent's signed response over crawl-based mutual-assertion inference. The
email-notification SHOULD continues to apply as a fallback when no
brand-agent is advertised. The RFC supersedes the typed-notification-
endpoint design originally sketched in #4521 — the verification surface
is interrogative, not notification-based.

Additive — no changes to brand.json itself; no migration burden.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oduct/docs (#4540)

Address all blockers and strong-should-fix items from the three-expert
review on PR #4540. Open questions resolved with consensus positions
baked into the contract.

Schemas
- Fix oneOf discriminator: success arm now carries symmetric not.errors,
  matching the error arm. Mirrors search-brands-response.json convention.
- Per-tool status enum subsets — verify_property excludes pending_review
  and licensed_in/out; verify_trademark excludes pending_review;
  verify_subsidiary excludes licensed_in/out. Each tool's response
  constrains the shared VerificationStatus to its applicable set.
- Add `transferring` to VerificationStatus — M&A in flight is distinct
  from pending_review.
- Drop redundant `license_type` from verify-trademark-response (status
  already carries the licensing relationship).
- Field-name consistency: `context_note` everywhere (verify_subsidiary
  previously had `dispute_reason`).
- `regions` empty-array ambiguity resolved with `"global"` sentinel
  matching request schema.
- Use-case authorization gains a registered starter set (advertising,
  endorsement, retail_listing, editorial, commercial_advertising,
  merchandise_resale) with additionalProperties: boolean for extensions.
- pending_review now REQUIRES expected_resolution_window_days; agents
  MUST transition or flip to unknown past the window.

RFC doc
- Conflict resolution promoted from open question to normative trust
  model. Full table covering all status × crawl-observation combinations.
- Signing key corrected: adcp_use: "response-signing" (not request-signing).
  Per the keys-per-purpose convention.
- "DRM-shaped" stays as analogy; lead framing is "federated authoritative
  verification" — what the spec audience actually wants.
- Motivation honest about what the surface does NOT fix: the sub-brand
  self-publishing problem is relocated, not solved. Partner gets typed
  state instead of silence; brand-side workflow gap remains.
- verify_property reframed: not a bid-time tool (sub-100ms budgets);
  inventory onboarding / supply-path curation / fraud / clearance.
- verify_trademark differentiator foregrounded: registries can't tell
  you authorized use cases or licensee posture.
- Deployment model section added: AAO-hosted as managed service for
  most members; self-hosted for holdcos/agencies with bandwidth; no
  brand-agent as graceful-degradation path.
- End-to-end Nike worked example added — six steps from discovery
  through full portfolio verification with cache guidance.
- Caching as normative SHOULD per-status (was prose).
- Rate-limiting as normative expectation: Retry-After header,
  prefer-cached-prior over hard error.
- Prior art expanded: ads.txt for inventory authority (complementary),
  WHOIS/RDAP for domain registration (different layer), trademark
  registries (registration facts only). No direct equivalent today.
- check_competitive_relationship moved to "indefinitely deferred"
  (politically loaded; brands won't publicly enumerate enemies).
- Authorization-tier table promoted to single authoritative source;
  task pages refer back.
- Resolved-decisions section replaces Open Questions.

Task pages
- Parallelism fixed for RAG retrieval: each page inlines core content
  (trust model row-table, caching, error codes) rather than cross-
  referencing back to verify_subsidiary_claim. Cross-links remain for
  full normative detail.
- Each page adds Capability discovery snippet showing get_adcp_capabilities
  advertisement.
- Each page's CodeGroup includes a Response (error) example.
- verify_property "When to use" reframed away from bid-time.
- verify_trademark leads with the differentiator vs registries.

Nav and Conformance
- docs.json: new Proposals group under brand-protocol nav surfaces the
  RFC.
- brand-json Conformance bullet updated to cite the correct signing
  key (response-signing) and call out that signed disputed/not_ours
  overrides leaf-side house_domain claims.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley and others added 5 commits May 14, 2026 10:47
… baseline

CI check test:oneof-discriminators flagged the three new verification
response schemas as undiscriminated oneOfs. They follow the same shape
as the existing brand-protocol responses already in the baseline
(get-brand-identity-response, search-brands-response, etc.) — success
arm required:[status] vs error arm required:[errors] — and don't have
a natural cross-arm discriminator beyond the existing required-field
asymmetry.

Ratcheting the baseline via --update --accept-new is the documented path
for this pattern. The three entries:
- brand/verify-property-response.json
- brand/verify-subsidiary-claim-response.json
- brand/verify-trademark-response.json

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ify aging contract enforcement

Two pieces of feedback addressed.

Verification as a gate, not a signal
- verify_property and verify_subsidiary_claim now lead with "this is a
  prerequisite gate — check before you proceed, not after." The gating
  semantics are what justify the round-trip cost; consuming the answer
  post-decision means you're using the wrong tool.
- When-to-use sections reframed around gating points: inventory
  onboarding (gate the catalog entry), creative clearance (gate
  approval), fraud escalation (gate the action), brand-relationship
  establishment (gate governance trust extension).
- verify_trademark already framed correctly; minor lead tightening.

pending_review aging contract honesty
- MUST language stays — declared intent + defined fallback is better
  than today's silent pending limbo. But the enforcement story now
  reads as it actually is: agent-side, not spec-level. Most brand-side
  day-one deployments won't ship automated pending→unknown flipping.
- New dedicated subsection in the RFC: "pending_review aging and the
  crawl safety net." Spells out: enforcement is agent-side; day-one
  deployments will lag; consumer-side fallback to crawl on stale
  pending_review IS the safety net; MUST is fine because the fallback
  is graceful.
- Trust-model row updated to instruct consumers to treat stale
  pending_review as effectively unknown.
- Resolved-decision #2 points at the new section.
- verify_subsidiary_claim task page mirrors the same framing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_property request

Platform-agnosticism lint flagged the store enum in
brand/verify-property-request.json (apple/google/amazon/roku) because
the new file path doesn't match the existing brand.json allowlist
entries. Same canonical-identifier justification applies: these are
the names of the app stores, not vendor-promotional values.

Factoring the store enum into a shared enums/property-store.json
(referenced from both brand.json and the verify_property request) is
the right long-term cleanup — protocol-expert review flagged it on
PR #4540. Tracking separately; for now the allowlist mirrors the
existing brand.json pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, not agent-replaces

Bokelley caught a real trust-model hole on PR #4540: as drafted, "the
agent wins" let a malicious or mistaken house claim subsidiaries it
doesn't own by returning `owned` to verify_subsidiary_claim without
the leaf reciprocating. The mutual-assertion floor was the exact
protection being bypassed.

The fix is to recognize the trust model is asymmetric by direction:

Rejection direction (agent wins unilaterally)
  agent says `disputed` / `not_ours` → authoritative. A brand has
  standing to refuse association regardless of what any other party
  publishes. Worst case is a brand denying a relationship it actually
  has — that's the brand's prerogative.

Assertion direction (mutual assertion still required)
  agent says `owned` / `pending_review` / `transferring` → informative
  but NOT trust-extending on its own. The leaf must still reciprocate
  via its `house_domain` claim (or its own brand-agent) before
  relationship trust extends. The agent provides real-time confirmation,
  richer states the crawl can't express, and signed authenticity — but
  it does NOT replace the mutual-assertion check.

Without this asymmetry, a malicious house unilaterally claims
subsidiaries and consumers extend trust on signature alone. Mutual
assertion exists precisely to prevent this.

Changes
- brand-json.mdx Conformance bullet: framing changed from
  "agent-preferred" → "agent-augmented." Split the override semantics
  by direction: rejection overrides leaf, assertion does NOT.
- RFC Trust model section: rewritten with two tables (rejection and
  assertion), explicit malicious-house scenario walkthrough, single
  "Mutual assertion remains the trust floor for positive trust
  extension" thesis statement.
- RFC Resolved-decisions #3: clarify "agent wins" applies to
  rejection direction only.
- verify_subsidiary_claim task page: trust-model table split into
  rows that condition on whether the leaf reciprocates. The
  agent's `owned` without reciprocation is now explicitly NOT
  trusted.
- verify_property task page: same asymmetric framing. Assertion-side
  requires cross-check against `brand.json` `properties[]` plus
  DNS/TLS evidence of control over the identifier.
- verify_trademark task page: same. Assertion-side requires
  registry record; agent is authority over licensee posture and use
  cases, not over registration ownership.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… out-of-scope

Bokelley flagged on PR #4540: verify_trademark returns licensed_in /
licensed_out because these relationships are real (Marriott franchisees,
music catalog licensing, jurisdictional rights splits — "I licensed my
catalog to someone in CN, manage it myself everywhere else"). But
brand.json doesn't have a publishing surface for standing licensed
relationships parallel to brand_refs[] for ownership.

The rights protocol (rights_agent + acquire_rights) handles transactional
licensing — negotiate a deal — not standing declarations like "I license
the MARRIOTT mark from Marriott Corp in DE, FR" or "I've granted Acme
Distribution rights to my catalog in CN."

This is a real protocol gap, too big to close in this RFC. Naming it
explicitly as out-of-scope so future readers don't think the omission
was accidental, and so the verify_trademark answers carry the right
caveat: the licensed states come from the brand-agent's internal
records, not from a crawlable static-file declaration.

Follow-up issue tracks the design: likely needs licensed_from[] /
licensed_to[] on the brand definition with jurisdictional scoping,
mutual-assertion shape parallel to brand_refs[], and integration with
the rights protocol's available_uses / right_types vocabulary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley and others added 3 commits May 16, 2026 06:47
…ith claim_type discriminator

Per Brian's "we're pretty good across AdCP of not having too many tools"
feedback: the three (eventually four) per-dimension verification tools
are really one operation — answer a verification question — discriminated
by which facet of identity is being verified. Tool-count economy beats
the per-tool-typing the protocol expert preferred; new claim types
become payload-discriminator additions, not new tools.

The brand-agent is brand.json hidden behind a server. Two affordances:
read (get_brand_identity) and ask-specific-questions (verify_brand_claim).
One tool per operation, not per question dimension.

Schema
- New: brand/verify-brand-claim-request.json with schema-level
  discriminator on claim_type. Four claim types in v1: subsidiary,
  parent (new — leaf-side mirror), property, trademark. Each variant
  has claim_type as a const plus the per-claim-type claim payload.
- New: brand/verify-brand-claim-response.json with claim_type echoed,
  shared status from VerificationStatus, and a per-claim-type details
  object (documented in the task page rather than nested-oneOf in the
  schema, to keep the schema simple).
- Deleted: six per-dimension schemas (verify-subsidiary-claim,
  verify-property, verify-trademark request+response each). Removed
  their entries from the oneOf-discriminators baseline; added one
  new entry for verify-brand-claim-response.json (same success/error
  pattern as other brand responses — discriminated success arm with
  required:[claim_type, status]).
- Platform-agnostic allowlist updated: apple/google/amazon/roku
  scoped to the new request file rather than the old verify_property
  file.

Task page
- New: docs/brand-protocol/tasks/verify_brand_claim.mdx. Per-claim-type
  subsections cover the request shape, applicable statuses, and the
  details fields each claim type returns. Capability discovery
  documents the per-tool supported_claim_types extension on
  get_adcp_capabilities so agents can advertise partial support.
- Deleted: three old task pages.

RFC body
- Restructured around the single tool. Adds the brand-agent-as-
  queryable-brand.json framing (the conceptual answer to "why one tool
  not three"). New end-to-end Nike example shows mutual assertion
  completing at the agent layer via subsidiary + parent claim types
  in a single round-trip pair, no static-file crawl.
- Resolved-decisions section adds "one tool vs many" (resolved: one)
  and "leaf-side mirror" (resolved: parent claim type).
- Trust model and aging-contract sections updated to reference the
  unified tool.

Conformance
- brand-json.mdx Agent-augmented-verification bullet updated to reference
  verify_brand_claim with claim_type rather than three separate task names.
- docs.json nav: three Tasks entries collapsed to one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs.json has two copies of the brand-protocol Tasks group (top-level
nav and the platform-builder reading-path nav). My earlier consolidation
commit caught the first copy via replace_all but the second has
different indentation, so the per-dimension entries lingered. Brian
caught it on review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rification surface

Ships the unified verification tool as a normative spec, not a proposal.
The PR is the WG-facing proposal; the docs are the shipping artifact.

Folds load-bearing RFC content into brand-json.mdx:
- New "Agent-augmented verification" subsection under Mutual-assertion
  trust model. Carries the direction-asymmetric trust table (rejection
  unilateral, assertion mutual-required) and the agent-to-agent mutual
  assertion path for when both sides publish brand-agents.
- Self-healing section updated: agent path is no longer a "planned
  follow-up" but the documented alternative to email when brand-agents
  are advertised.
- Conformance bullet drops "(proposed)" framing; cites the new
  subsection.
- Out-of-scope adds the standing-licensed-relationships gap as a
  fourth bullet — the licensed_in/licensed_out states are real and
  surfaceable via the agent, but the static brand.json publishing
  surface for them is a future RFC alongside the rights-protocol team.

Task page (verify_brand_claim):
- Drops the <Warning> "Proposed (RFC)" callout.
- Aging-contract prose moved inline (was a link to a deleted RFC
  section); content unchanged — agent-side enforcement, consumer-side
  fallback to crawl on stale pending_review.
- Cross-references to the RFC replaced with cross-references to
  brand-json.mdx § Agent-augmented verification.
- Authorization tier table no longer claims the RFC is the
  "authoritative" source — the task page stands on its own.

Cleanup:
- docs/brand-protocol/proposals/brand-verification-rfc.mdx deleted.
- Proposals nav group dropped from docs.json.
- Changeset renamed brand-verification-rfc.md → verify-brand-claim.md;
  rewritten from "Draft RFC" framing to minor-bump ship framing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley changed the title docs(brand-protocol): RFC — brand verification surface (verify_subsidiary_claim / verify_property / verify_trademark) feat(brand-protocol): verify_brand_claim — federated authoritative verification May 16, 2026
…, rejection examples, licensed_in reciprocation, field clarity

Address protocol/product/docs expert review on PR #4540. All three said
ship; this lands the substantive refinements.

Trust model (highest-risk product gap)
- Task page now LEADS with "two calls, not one" — the load-bearing
  rule of the asymmetric trust model that partners will shortcut on
  if it isn't front and center. Spells out per-claim-type reciprocation
  (parent for subsidiary, properties[]+DNS for property, registry+
  licensor for trademark) before the per-claim-type tables.
- brand-json.mdx Agent-augmented subsection: adds the
  consistency-not-standing sentence. Mutual assertion at the agent
  layer proves the two parties agree; it doesn't prove either has
  legitimate authority. Domain control + TLS + the consumer's real-
  world identity check remain the final trust gate.
- Task page: licensed_in reciprocation — consumers SHOULD treat
  licensed_in as unverified until the named licensor reciprocates
  licensed_out. Same mutual-assertion shape across the licensing edge.

Rejection-direction examples
- Added not_ours / disputed examples on subsidiary, property, and
  trademark. Rejection is the only single-side authoritative direction
  in the trust model; reviewers flagged that examples only showed it
  on parent. Now shown on every claim type.

Field clarity
- subsidiary, parent, property: added Request `claim` fields tables
  with Required/Notes columns. observed_at and claimant_says are now
  documented fields rather than mystery extras in JSON examples.
- parent details.house_domain: restricted to status ∈ {owned,
  transferring}. NOT returned for pending_review — the leaf hasn't
  yet accepted the parent claim. Protocol-expert catch.
- property: added a multi-country example (nike.com global e-comm)
  to show details.regions can exceed the request's single region
  filter. Documented the request region (singular, scoped) vs
  response regions (plural, applicable set) distinction.
- expected_resolution_window_days cell split into Tier / Returned-
  when / Notes columns rather than mixing all three in one cell.
- UNSUPPORTED_CLAIM_TYPE promoted to its own error code with a
  table row, replacing the overload onto INVALID_INPUT.

Minimum viable adoption
- New task-page section "Minimum viable adoption" — partners can
  ship one slice (property only, or subsidiary+parent, or trademark
  only) without implementing all four claim types. Lowers surface-
  area bounce risk; documents supported_claim_types capability
  semantics.

brand-json.mdx
- Trademarks section: <Info> callout that licensed_in/licensed_out
  states are queryable via the brand-agent today; static publishing
  surface is a future RFC. Surfaces the agent/static-file gap at
  the moment readers actually encounter it (the Trademarks section)
  rather than only in Out of scope.

verification-status.json
- Description rewritten — was still naming the three deleted tools
  (verify_subsidiary_claim / verify_property / verify_trademark).
  Now correctly references verify_brand_claim with claim_type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant